/**
 * @file TcpApp_client.c
 * @brief TcpApp/TCP client
 *
 * @section License
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 *
 * Copyright (C) 2010-2025 Oryx Embedded SARL. All rights reserved.
 *
 * This file is part of CycloneTCP Open.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * @author Oryx Embedded SARL (www.oryx-embedded.com)
 * @version 2.5.2
 **/

// Switch to the appropriate trace level
#define TRACE_LEVEL TCP_APP_TRACE_LEVEL

// Dependencies
#include "client/tcp_client_app.h"
#include "client/tcp_client_app_pdu.h"
#include "client/tcp_client_app_transport.h"
#include "client/tcp_client_app_misc.h"
#include "debug.h"

/**
 * @brief Initialize TcpApp/TCP client context
 * @param[in] context Pointer to the TcpApp/TCP client context
 * @return Error code
 **/

error_t TcpAppClientInit(TcpAppClientContext *context)
{
   // Make sure the TcpApp/TCP client context is valid
   if (context == NULL)
      return ERROR_INVALID_PARAMETER;

   // Clear TcpApp/TCP client context
   osMemset(context, 0, sizeof(TcpAppClientContext));

   // Initialize TcpApp/TCP client state
   context->state = TCP_APP_CLIENT_STATE_DISCONNECTED;

   // Default timeout
   context->timeout = TCP_APP_CLIENT_DEFAULT_TIMEOUT;

   // Successful initialization
   return NO_ERROR;
}

/**
 * @brief Set timeout value for blocking operations
 * @param[in] context Pointer to the TcpApp/TCP client context
 * @param[in] timeout Timeout value, in milliseconds
 * @return Error code
 **/

error_t TcpAppClientSetTimeout(TcpAppClientContext *context, systime_t timeout)
{
   // Make sure the TcpApp/TCP client context is valid
   if (context == NULL)
      return ERROR_INVALID_PARAMETER;

   // Save timeout value
   context->timeout = timeout;

   // Successful processing
   return NO_ERROR;
}

/**
 * @brief Bind the TcpApp/TCP client to a particular network interface
 * @param[in] context Pointer to the TcpApp/TCP client context
 * @param[in] interface Network interface to be used
 * @return Error code
 **/

error_t TcpAppClientBindToInterface(TcpAppClientContext *context,
                                    NetInterface *interface)
{
   // Make sure the TcpApp/TCP client context is valid
   if (context == NULL)
      return ERROR_INVALID_PARAMETER;

   // Explicitly associate the TcpApp/TCP client with the specified interface
   context->interface = interface;

   // Successful processing
   return NO_ERROR;
}

/**
 * @brief Establish connection with the TcpApp/TCP server
 * @param[in] context Pointer to the TcpApp/TCP client context
 * @param[in] serverIpAddr IP address of the server to connect to
 * @param[in] serverPort TCP port number that will be used
 * @return Error code
 **/

error_t TcpAppClientConnect(TcpAppClientContext *context,
                            const IpAddr *serverIpAddr, uint16_t serverPort)
{
   error_t error;

   // Check parameters
   if (context == NULL || serverIpAddr == NULL)
      return ERROR_INVALID_PARAMETER;

   // Initialize status code
   error = NO_ERROR;

   // Establish connection with the TcpApp/TCP server
   while (!error)
   {
      // Check current state
      if (context->state == TCP_APP_CLIENT_STATE_DISCONNECTED)
      {
         // Open network connection
         error = TcpAppClientOpenConnection(context);

         // Check status code
         if (!error)
         {
            // Save current time
            context->timestamp = osGetSystemTime();
            // Update TcpApp/TCP client state
            context->state = TCP_APP_CLIENT_STATE_CONNECTING;
         }
      }
      else if (context->state == TCP_APP_CLIENT_STATE_CONNECTING)
      {
         // Establish network connection
         error = TcpAppClientEstablishConnection(context, serverIpAddr,
                                                 serverPort);

         // Check status code
         if (error == NO_ERROR)
         {
            // Update TcpApp/TCP client state
            context->state = TCP_APP_CLIENT_STATE_CONNECTED;
         }
         else if (error == ERROR_WOULD_BLOCK || error == ERROR_TIMEOUT)
         {
            // Check whether the timeout has elapsed
            error = TcpAppClientCheckTimeout(context);
         }
         else
         {
            // A communication error has occurred
         }
      }
      else if (context->state == TCP_APP_CLIENT_STATE_CONNECTED)
      {
         // The TcpApp/TCP client is connected
         break;
      }
      else
      {
         // Invalid state
         error = ERROR_WRONG_STATE;
      }
   }

   // Failed to establish connection with the TcpApp/TCP server?
   if (error != NO_ERROR && error != ERROR_WOULD_BLOCK)
   {
      // Clean up side effects
      TcpAppClientCloseConnection(context);
      // Update TcpApp/TCP client state
      context->state = TCP_APP_CLIENT_STATE_DISCONNECTED;
   }

   // Return status code
   return error;
}

/**
 * @brief TcpAppClientUserSendData
 *
 * This function code is used to read the contents of a contiguous block of
 * holding registers in a remote device. The request specifies the starting
 * register address and the number of registers
 *
 * @param[in] context Pointer to the TcpApp/TCP client context
 * @param[in] address Starting register address
 * @param[in] quantity Number of registers
 * @param[out] value Value of the holding registers
 * @return Error code
 **/

error_t TcpAppClientUserSendData(TcpAppClientContext *context,
                                 const uint8_t *data, size_t length, bool_t resp_enable)
{
   error_t error;

   // Check parameters
   if (context == NULL || data == NULL)
      return ERROR_INVALID_PARAMETER;

   // The number of registers must be in range 1 to ...
   if (length < 1 || length > sizeof(context->requestAdu))
      return ERROR_INVALID_PARAMETER;

   // Initialize status code
   error = NO_ERROR;

   // Perform TcpApp request/response transaction
   while (!error)
   {
      // Check current state
      if (context->state == TCP_APP_CLIENT_STATE_CONNECTED)
      {
		 // resp reset
         error = TcpAppClientSendDataExec(context, data, length);
      }
      else if (context->state == TCP_APP_CLIENT_STATE_SENDING)
	  {
		  // Transaction
		 error = TcpAppClientTransaction(context);
	  }
	  else if  (context->state == TCP_APP_CLIENT_STATE_RECEIVING)
      {
         if (resp_enable)
         {
            // Send TcpApp request and wait for a matching response
            error = TcpAppClientTransaction(context);
         }
         else
         {
            // complete
            context->state = TCP_APP_CLIENT_STATE_COMPLETE;
         }
      }
      else if (context->state == TCP_APP_CLIENT_STATE_COMPLETE)
      {
         if (resp_enable)
         {
            error = TcpAppClientSendDataResp(context);
         }

         // The TcpApp transaction is complete
         context->state = TCP_APP_CLIENT_STATE_CONNECTED;
         break;
      }
      else
      {
         // Invalid state
         error = ERROR_NOT_CONNECTED;
      }
   }

   // Return status code
   return error;
}

/**
 * @brief TcpAppClientReadResp
 *
 * This function read data
 *
 * @param[in] context Pointer to the TcpApp/TCP client context
 * @param[in] rdata Starting register address
 * @param[in] length Number of registers
 * @param[out] length_back
 * @return Error code
 **/
error_t TcpAppClientReadResp(TcpAppClientContext *context, void *rdata, size_t length, size_t *length_back)
{
	error_t error;
	
	*length_back = 0;
    error = TcpAppClientReceiveData(context, rdata , length, length_back, SOCKET_FLAG_DONT_WAIT | SOCKET_FLAG_NO_DELAY);
	
	return error;
}

/**
 * @brief Gracefully disconnect from the TcpApp/TCP server
 * @param[in] context Pointer to the TcpApp/TCP client context
 * @return Error code
 **/

error_t TcpAppClientDisconnect(TcpAppClientContext *context)
{
   error_t error;

   // Make sure the TcpApp/TCP client context is valid
   if (context == NULL)
      return ERROR_INVALID_PARAMETER;

   // Initialize status code
   error = NO_ERROR;

   // Gracefully disconnect from the TcpApp/TCP server
   while (!error)
   {
      // Check current state
      if (context->state == TCP_APP_CLIENT_STATE_CONNECTED)
      {
         // Save current time
         context->timestamp = osGetSystemTime();
         // Update TcpApp/TCP client state
         context->state = TCP_APP_CLIENT_STATE_DISCONNECTING;
      }
      else if (context->state == TCP_APP_CLIENT_STATE_DISCONNECTING)
      {
         // Shutdown connection
         error = TcpAppClientShutdownConnection(context);

         // Check status code
         if (error == NO_ERROR)
         {
            // Close connection
            TcpAppClientCloseConnection(context);
            // Update TcpApp/TCP client state
            context->state = TCP_APP_CLIENT_STATE_DISCONNECTED;
         }
         else if (error == ERROR_WOULD_BLOCK || error == ERROR_TIMEOUT)
         {
            // Check whether the timeout has elapsed
            error = TcpAppClientCheckTimeout(context);
         }
         else
         {
            // A communication error has occurred
         }
      }
      else if (context->state == TCP_APP_CLIENT_STATE_DISCONNECTED)
      {
         // The TcpApp/TCP client is disconnected
         break;
      }
      else
      {
         // Invalid state
         error = ERROR_WRONG_STATE;
      }
   }

   // Failed to gracefully disconnect from the TcpApp/TCP server?
   if (error != NO_ERROR && error != ERROR_WOULD_BLOCK)
   {
      // Close connection
      TcpAppClientCloseConnection(context);
      // Update TcpApp/TCP client state
      context->state = TCP_APP_CLIENT_STATE_DISCONNECTED;
   }

   // Return status code
   return error;
}

/**
 * @brief Close the connection with the TcpApp/TCP server
 * @param[in] context Pointer to the TcpApp/TCP client context
 * @return Error code
 **/

error_t TcpAppClientClose(TcpAppClientContext *context)
{
   // Make sure the TcpApp/TCP client context is valid
   if (context == NULL)
      return ERROR_INVALID_PARAMETER;

   // Close connection
   TcpAppClientCloseConnection(context);
   // Update TcpApp/TCP client state
   context->state = TCP_APP_CLIENT_STATE_DISCONNECTED;

   // Successful processing
   return NO_ERROR;
}

/**
 * @brief Release TcpApp/TCP client context
 * @param[in] context Pointer to the TcpApp/TCP client context
 **/

void TcpAppClientDeinit(TcpAppClientContext *context)
{
   // Make sure the TcpApp/TCP client context is valid
   if (context != NULL)
   {
      // Close connection
      TcpAppClientCloseConnection(context);

      // Clear TcpApp/TCP client context
      osMemset(context, 0, sizeof(TcpAppClientContext));
   }
}
